﻿/*
Copyright © Naveen Hegde
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- Neither the name of the author, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior written 
permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Windows.Forms;

namespace NvinInstaller
{
    class WixFileGenerator
    {
        static int count = 0;
        static XmlDocument doc;
        static XmlElement rootNode;
        static XmlElement productNode;

        public static void GenerateWixFile(ApplicationManager manager)
        {         
            // Create Root element
            CreateRootElement();
            // Create product node
            productNode = CreateProductNode(manager.Properties);
            rootNode.AppendChild(productNode);
            // append package node to product node
            productNode.AppendChild(CreatePackageNode(manager.Properties));
            // append media node to product node
            productNode.AppendChild(CreateMediaNode());
            // append property node to product node
            productNode.AppendChild(CreatePropertyNode(manager.Properties));
            // append directory node to product node
            productNode.AppendChild(CreateDirectoryNode(manager.Properties, manager.ApplicationTree));
            // append feature node to product node
            productNode.AppendChild(CreateFeatureNode(manager.Properties,manager.ApplicationTree,manager.FeatureTree));
            // append UiRef node to product node
            productNode.AppendChild(CreateUiRef1Node(manager));
            productNode.AppendChild(CreateUiRef2Node());

            // save Wix file
            if (File.Exists(Globals.WixFile) == false)
            {
                FileInfo info = new FileInfo(Globals.WixFile);
                if (Directory.Exists(info.Directory.FullName) == false)
                {
                    Directory.CreateDirectory(info.Directory.FullName);
                }

                FileStream stream = File.Create(Globals.WixFile);
                stream.Close();
            }

            doc.Save(Globals.WixFile);
        }

        private static void CreateRootElement()
        {
            doc = new XmlDocument();
            XmlDeclaration xmlDeclaration = doc.CreateXmlDeclaration("1.0", "utf-8", null);

            // Create the root element
            rootNode = doc.CreateElement("Wix");
            doc.InsertBefore(xmlDeclaration, doc.DocumentElement);
            doc.AppendChild(rootNode);
        }

        private static XmlElement CreateProductNode(ProjectProperties properties)
        {
            XmlElement productNode = doc.CreateElement("Product");
            productNode.SetAttribute("Name", properties.ProductName);
            productNode.SetAttribute("Id", Guid.NewGuid().ToString());
            productNode.SetAttribute("Language", "1033");
            productNode.SetAttribute("Codepage", "1252");
            productNode.SetAttribute("Version", properties.Version);
            productNode.SetAttribute("Manufacturer", properties.Manufacturer);

            return productNode;
        }

        private static XmlElement CreatePackageNode(ProjectProperties properties)
        {
            XmlElement packageNode = doc.CreateElement("Package");
            packageNode.SetAttribute("Id", "????????-????-????-????-????????????");
            packageNode.SetAttribute("Keywords", properties.Manufacturer);
            packageNode.SetAttribute("Description", properties.Description);
            packageNode.SetAttribute("Comments", properties.Comments);
            packageNode.SetAttribute("Manufacturer", properties.Manufacturer);
            packageNode.SetAttribute("InstallerVersion", "100");
            packageNode.SetAttribute("Languages", "1033");
            packageNode.SetAttribute("Compressed", "yes");
            packageNode.SetAttribute("SummaryCodepage", "1252");

            return packageNode;
        }

        private static XmlElement CreateMediaNode()
        {
            XmlElement mediaNode = doc.CreateElement("Media");
            mediaNode.SetAttribute("Id", "1");
            mediaNode.SetAttribute("Cabinet", "NvinInstaller.cab");
            mediaNode.SetAttribute("EmbedCab", "yes");
            mediaNode.SetAttribute("DiskPrompt", "CD-ROM #1");

            return mediaNode;
        }

        private static XmlElement CreatePropertyNode(ProjectProperties properties)
        {
            XmlElement propertyNode = doc.CreateElement("Property");
            propertyNode.SetAttribute("Id", "DiskPrompt");
            propertyNode.SetAttribute("Value", properties.Description);

            return propertyNode;
        }

        private static XmlElement CreateDirectoryNode(ProjectProperties properties, TreeView applicationDirectory)
        {
            #region Main directories
            XmlElement target = doc.CreateElement("Directory");
            target.SetAttribute("Id", "TARGETDIR");
            target.SetAttribute("Name", "SourceDir");

            XmlElement programFiles = doc.CreateElement("Directory");
            programFiles.SetAttribute("Id", "ProgramFilesFolder");
            programFiles.SetAttribute("Name", "PFiles");

            XmlElement companyDir = doc.CreateElement("Directory");
            string shortName1 = properties.Manufacturer.Replace(' ', '_');
            if (shortName1.Length > 8)
            {
                shortName1 = shortName1.Remove(8, shortName1.Length - 8);
            }
            companyDir.SetAttribute("Id", shortName1);
            companyDir.SetAttribute("Name", shortName1);
            if(properties.Manufacturer.Length >8)
                companyDir.SetAttribute("LongName", properties.Manufacturer);

            XmlElement productDir = doc.CreateElement("Directory");
            productDir.SetAttribute("Id", "INSTALLDIR");            
            string shortName8 = properties.ProductName.Replace(' ', '_');
            if (shortName8.Length > 8)
            {
                shortName8 = shortName8.Remove(8, shortName8.Length - 8);
            }
            productDir.SetAttribute("Name", shortName8);
            if(properties.ProductName.Length > 8)
                productDir.SetAttribute("LongName", properties.ProductName);
            #endregion

            foreach (ApplicationDirectoryNode node in applicationDirectory.Nodes)
            {
                if (node.Type == ApplicationDirNodeType.Directory)
                {
                    AddDirectory(node,productDir);
                }
                else if (node.Type == ApplicationDirNodeType.File)
                {
                    AddFile(node,productDir);
                }
            }

            #region Define Desktop and Program files folder

            companyDir.AppendChild(productDir);
            programFiles.AppendChild(companyDir);

            XmlElement dir1 = doc.CreateElement("Directory");
            dir1.SetAttribute("Id", "ProgramMenuFolder");
            dir1.SetAttribute("Name", "PMenu");
            //dir1.SetAttribute("LongName", "Programs");

            XmlElement dir11 = doc.CreateElement("Directory");
            dir11.SetAttribute("Id", "ProgramMenuDir");
            if (properties.ProductName.Length < 8)
                dir11.SetAttribute("Name", properties.ProductName);
            else
            {
                dir11.SetAttribute("Name", properties.ProductName.Remove(0,8));
                dir11.SetAttribute("LongName", properties.ProductName);
            }

            dir1.AppendChild(dir11);

            XmlElement dir3 = doc.CreateElement("Directory");
            dir3.SetAttribute("Id", "DesktopFolder");
            dir3.SetAttribute("Name", "Desktop");

            target.AppendChild(programFiles);
            target.AppendChild(dir1);
            target.AppendChild(dir3);

            #endregion

            return target;
        }

        private static void AddFile(ApplicationDirectoryNode node, XmlElement productDir)
        {
            // add components element
            XmlElement comp = doc.CreateElement("Component");
            comp.SetAttribute("Id", "F"+((Component)node.Content).ID.Replace('-','_'));
            comp.SetAttribute("Guid", Guid.NewGuid().ToString());

            string shortName = node.Text.Replace(' ','_');
            string shortName8 = shortName;
            if (shortName8.Length > 8)
            {
                shortName8 = shortName8.Remove(8, shortName8.Length - 8);
            }

            XmlElement fileElement = doc.CreateElement("File");
            fileElement.SetAttribute("Id", "File"+count++);
            fileElement.SetAttribute("Name", shortName8);
            fileElement.SetAttribute("LongName", shortName);
            fileElement.SetAttribute("DiskId", "1");
            fileElement.SetAttribute("Source", ((Component)node.Content).Source);
            fileElement.SetAttribute("Vital", "yes");

            #region Desktop shortcut and Program Menu
            Component cmp = ((Component)node.Content);
            if (cmp.CreateDesktopShortcut)
            {
                // create and append shortcut element
                XmlElement shortcut = doc.CreateElement("Shortcut");
                shortcut.SetAttribute("Id", "menu" + count);
                shortcut.SetAttribute("Directory", "DesktopFolder");
                shortcut.SetAttribute("Name", shortName8);
                shortcut.SetAttribute("LongName", shortName);

                fileElement.AppendChild(shortcut);
            }
            if (cmp.CreateMenuShortcut)
            {
                // create and append shortcut element
                XmlElement shortcut = doc.CreateElement("Shortcut");
                shortcut.SetAttribute("Id", "desk" + count);
                shortcut.SetAttribute("Directory", "ProgramMenuDir");
                shortcut.SetAttribute("Name", shortName8);
                shortcut.SetAttribute("LongName", shortName);

                fileElement.AppendChild(shortcut);
            }
            #endregion

            comp.AppendChild(fileElement);
            productDir.AppendChild(comp);
        }

        private static void AddDirectory(ApplicationDirectoryNode node, XmlElement productDir)
        {
            XmlElement directoryElement = doc.CreateElement("Directory");
            directoryElement.SetAttribute("Id", "dir"+count++);
            directoryElement.SetAttribute("LongName", node.Text);

            string shortName = node.Text.Replace(' ','_');
            string shortName8 = shortName;
            if (shortName8.Length > 8)
            {
                shortName8 = shortName8.Remove(8, shortName8.Length - 8);
            }
            directoryElement.SetAttribute("Name", shortName8);

            productDir.AppendChild(directoryElement);

            foreach (ApplicationDirectoryNode childnode in node.Nodes)
            {
                if (childnode.Type == ApplicationDirNodeType.Directory)
                {
                    AddDirectory(childnode, directoryElement);
                }
                else if (childnode.Type == ApplicationDirNodeType.File)
                {
                    AddFile(childnode, directoryElement);
                }
            }
        }

        private static XmlElement CreateFeatureNode(ProjectProperties properties,TreeView applicationDirectory,TreeView featureTree)
        {
            XmlElement featureElement = doc.CreateElement("Feature");
            featureElement.SetAttribute("Id", "Fea"+Guid.NewGuid().ToString().Replace('-','_'));
            featureElement.SetAttribute("Title", properties.ProductName);
            featureElement.SetAttribute("Description", properties.Description);
            featureElement.SetAttribute("Display", "expand");
            featureElement.SetAttribute("Level", "1");
            featureElement.SetAttribute("ConfigurableDirectory", "INSTALLDIR");

            for (int i = 0; i < featureTree.Nodes.Count; i++)
            {
                TreeNode node = featureTree.Nodes[i];
                XmlElement feature = doc.CreateElement("Feature");
                feature.SetAttribute("Id", node.Text.Replace(' ','_') + Guid.NewGuid().ToString().Replace('-', '_'));
                feature.SetAttribute("Title", node.Text);
                feature.SetAttribute("Level", "1");
                // loop through application tree and add component ref
                foreach (ApplicationDirectoryNode appNode in applicationDirectory.Nodes)
                {
                    if (appNode.Type == ApplicationDirNodeType.Directory)
                    {
                        DirectoryFiles(appNode, feature);
                    }
                    if (appNode.Type == ApplicationDirNodeType.File)
                    {
                        AddComponentRef(appNode,feature);
                    }
                }

                featureElement.AppendChild(feature);
            }

            return featureElement;
        }

        private static void DirectoryFiles(ApplicationDirectoryNode appNode, XmlElement feature)
        {
            foreach (ApplicationDirectoryNode node in appNode.Nodes)
            {
                if (node.Type == ApplicationDirNodeType.Directory)
                {
                    DirectoryFiles(node, feature);
                }
                if (node.Type == ApplicationDirNodeType.File)
                {
                    AddComponentRef(node, feature);
                }
            }
        }

        private static void AddComponentRef(ApplicationDirectoryNode appNode, XmlElement feature)
        {
            XmlElement compElement = doc.CreateElement("ComponentRef");
            string name = (string)feature.Attributes["Title"].Value;
            Component cmp = (Component)appNode.Content;
            if (cmp.Feature.Equals(name))
            {
                compElement.SetAttribute("Id", "F" + ((Component)appNode.Content).ID.Replace('-', '_'));
                feature.AppendChild(compElement);
            }
        }

        public static XmlElement CreateUiRef1Node(ApplicationManager manager)
        {
            XmlElement refNode = doc.CreateElement("UIRef");
            switch (manager.Properties.UserInterfaceType)
            {
                case UIType.Complete:
                    refNode.SetAttribute("Id", "WixUI_Mondo");
                    break;
                case UIType.FeatureTree:
                    refNode.SetAttribute("Id", "WixUI_FeatureTree");
                    break;
                case UIType.InstallDirectory:
                    refNode.SetAttribute("Id", "WixUI_InstallDir");
                    break;
                case UIType.Minimal:
                    refNode.SetAttribute("Id", "WixUI_Minimal");
                    break;
            }            
            return refNode;
        }

        public static XmlElement CreateUiRef2Node()
        {
            XmlElement refNode = doc.CreateElement("UIRef");
            refNode.SetAttribute("Id", "WixUI_ErrorProgressText");

            return refNode;
        }

        public static XmlElement GetFeaturesNode(string productName, string description, XmlDocument doc)
        {
            XmlElement featureElement = doc.CreateElement("Feature");
            featureElement.SetAttribute("Id", "featureMain");
            featureElement.SetAttribute("Title", productName);
            featureElement.SetAttribute("Description", description);
            featureElement.SetAttribute("Display", "expand");
            featureElement.SetAttribute("Level", "1");
            featureElement.SetAttribute("ConfigurableDirectory", "INSTALLDIR");

            for (int i = 0; i < count; i++)
            {
                XmlElement compElement = doc.CreateElement("ComponentRef");
                compElement.SetAttribute("Id", "comp" + i.ToString());
                featureElement.AppendChild(compElement);
            }

            return featureElement;
        }
    }
}
